/*------------------------------------------------------------------------
 * sockets.c Library from material seen in course 'Protocolos de Comunicacion',
 * ITBA, Buenos Aires, Argentina 
 *------------------------------------------------------------------------
 */


#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>

#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include "sockets.h"

#define SOCK_TIMEOUT	10			/* recv timeout in seconds */

#ifndef	INADDR_NONE
#define	INADDR_NONE	0xffffffff
#endif	/* INADDR_NONE */

extern int	errno;

void errexit(const char *format, ...);

/*------------------------------------------------------------------------
 * connectsock - allocate & connect a socket using TCP or UDP
 *------------------------------------------------------------------------
 */
int
connectsock(const char *host, const char *service, const char *transport )
/*
 * Arguments:
 *      host      - name of host to which connection is desired
 *      service   - service associated with the desired port
 *      transport - name of transport protocol to use ("tcp" or "udp")
 */
{
	int flag = 1;
	struct hostent	*phe;	/* pointer to host information entry	*/
	struct servent	*pse;	/* pointer to service information entry	*/
	struct protoent *ppe;	/* pointer to protocol information entry*/
	struct sockaddr_in sin;	/* an Internet endpoint address		*/
	struct linger linger;	/* to set no lingering after socket close */
	struct timeval tv;		/* to set a recv timeout */
	int	s, type;			/* socket descriptor and socket type	*/


	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;

    /* Map service name to port number */
	if ( (pse = getservbyname(service, transport)) )
		sin.sin_port = pse->s_port;
	else if ((sin.sin_port=htons((unsigned short)atoi(service))) == 0)
	{
		fprintf(stderr, "Error: can't get \"%s\" service entry\n", service);
		return -1;
	}

    /* Map host name to IP address, allowing for dotted decimal */
	if ( (phe = gethostbyname(host)) )
		memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
	else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
	{
		fprintf(stderr, "Error: can't get \"%s\" host entry\n", host);
		return -1;
	}

    /* Map transport protocol name to protocol number */
	if ( (ppe = getprotobyname(transport)) == 0)
	{
		fprintf(stderr, "Error: can't get \"%s\" protocol entry\n", transport);
		return -1;
	}

    /* Use protocol to choose a socket type */
	if (strcmp(transport, "udp") == 0)
		type = SOCK_DGRAM;
	else
		type = SOCK_STREAM;

    /* Allocate a socket */
	s = socket(PF_INET, type, ppe->p_proto);
	if (s < 0)
	{
		fprintf(stderr, "Error: can't create socket: %s\n", strerror(errno));
		return -1;
	}
	
	/* No lingering */
    linger.l_onoff = 1;
    linger.l_linger = 0;
    setsockopt(s, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
	
	/* No socket delay */
	setsockopt(s,IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
	
	/* Set time out */
	tv.tv_sec = SOCK_TIMEOUT;
	tv.tv_usec = 0;
	setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv));
	
	/* Connect the socket */
	if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		fprintf(stderr, "Error: can't connect to %s:%s: %s\n", host, service,
				strerror(errno));
		return -1;
	}	
	return s;
}


/*------------------------------------------------------------------------
 * connectTCP - connect to a specified TCP service on a specified host
 *------------------------------------------------------------------------
 */
int
connectTCP(const char *host, const char *service )
/*
 * Arguments:
 *      host    - name of host to which connection is desired
 *      service - service associated with the desired port
 */
{
	return connectsock( host, service, "tcp");
}

/*------------------------------------------------------------------------
 * connectUDP - connect to a specified UDP service on a specified host
 *------------------------------------------------------------------------
 */
int
connectUDP(const char *host, const char *service )
/*
 * Arguments:
 *      host    - name of host to which connection is desired
 *      service - service associated with the desired port
 */
{
	return connectsock( host, service, "udp");
}


/* passivesock.c - passivesock */

unsigned short	portbase = 0;	/* port base, for non-root servers	*/

/*------------------------------------------------------------------------
 * passivesock - allocate & bind a server socket using TCP or UDP
 *------------------------------------------------------------------------
 */
int
passivesock(const char *service, const char *transport, int qlen, const char* host)
/*
 * Arguments:
 *      service   - service associated with the desired port
 *      transport - transport protocol to use ("tcp" or "udp")
 *      qlen      - maximum server request queue length
 */
{
	struct servent	*pse;	/* pointer to service information entry	*/
	struct protoent *ppe;	/* pointer to protocol information entry*/
	struct sockaddr_in sin;	/* an Internet endpoint address		*/
	int	s, type;	/* socket descriptor and socket type	*/
	struct hostent	*phe;	/* pointer to host information entry	*/
	
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	
	if(!strcmp(host,"all"))
		sin.sin_addr.s_addr = INADDR_ANY;
	else
	{
		if ( (phe = gethostbyname(host)) )
			memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
		else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
		{
			fprintf(stderr, "Error: can't get \"%s\" host entry\n", host);
			return -1;
		}
	}
	

    /* Map service name to port number */
	if ( (pse = getservbyname(service, transport)) )
		sin.sin_port = htons(ntohs((unsigned short)pse->s_port)
			+ portbase);
	else if ((sin.sin_port=htons((unsigned short)atoi(service))) == 0)
	{
		fprintf(stderr, "Error: can't get \"%s\" service entry\n", service);
		return -1;
	}

    /* Map protocol name to protocol number */
	if ( (ppe = getprotobyname(transport)) == 0)
	{
		fprintf(stderr, "Error: can't get \"%s\" protocol entry\n", transport);
		return -1;
	}

    /* Use protocol to choose a socket type */
	if (strcmp(transport, "udp") == 0)
		type = SOCK_DGRAM;
	else
		type = SOCK_STREAM;

    /* Allocate a socket */
	s = socket(PF_INET, type, ppe->p_proto);
	if (s < 0)
	{
		fprintf(stderr, "Error: can't create socket: %s\n", strerror(errno));
		return -1;
	}

    /* Bind the socket */
	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		fprintf(stderr, "Error: can't bind to %s port: %s\n", service,
			strerror(errno));
		return -1;
	}
	if (type == SOCK_STREAM && listen(s, qlen) < 0)
	{
		fprintf(stderr, "Error: can't listen on %s port: %s\n", service,
			strerror(errno));
		return -1;
	}
	return s;
}

/*------------------------------------------------------------------------
 * passiveTCP - create a passive socket for use in a TCP server
 *------------------------------------------------------------------------
 */
int
passiveTCP(const char *service, int qlen, const char *host)
/*
 * Arguments:
 *      service - service associated with the desired port
 *      qlen    - maximum server request queue length
 */
{
	return passivesock(service, "tcp", qlen, host);
}


/*------------------------------------------------------------------------
 * passiveUDP - create a passive socket for use in a UDP server
 *------------------------------------------------------------------------
 */
int
passiveUDP(const char *service, const char *host)
/*
 * Arguments:
 *      service - service associated with the desired port
 */
{
	return passivesock(service, "udp", 0, host);
}


void 
errexit(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(1);
}
